{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# Multinetwork storage optimization with PowerModels.jl\n",
    "This tutorial describes how to run a storage optimization over multiple timesteps with a PowerModels.jl multinetwork\n",
    "together with pandapower.\n",
    "\n",
    "To run a storage optimization over multiple time steps, the power system data is copied n_timestep times internally.\n",
    "This is done efficiently in a julia script. Each network in the multinetwork dict represents a single time step. \n",
    "The input time series must be written to the loads and generators accordingly to each network. \n",
    "This is currently done by converting input time series to a dict, saving it as a json file and loading the data\n",
    "back in julia. This \"hack\" is probably just a temporary solution. \n",
    "\n",
    "Some notes:\n",
    "* only storages which are set as \"controllable\" are optimized\n",
    "* time series can be written to load / sgen elements only at the moment\n",
    "* output of the optimization is a dict containing pandas DataFrames for every optimized storage and time step   \n",
    "\n",
    "For more details on PowerModels storage model see:\n",
    "\n",
    "https://lanl-ansi.github.io/PowerModels.jl/stable/storage/ \n",
    "\n",
    "For more details on PowerModels multinetworks see:\n",
    "\n",
    "https://lanl-ansi.github.io/PowerModels.jl/stable/multi-networks/\n"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "# Installation\n",
    "You need the standard Julia, PowerModels, Ipopt and JuMP Installation (see the opf_powermodels.ipynb).\n",
    " "
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "# Run the storage optimization\n",
    "In order to start the optimization and visualize results, we follow four steps:\n",
    "1. Load the pandapower grid data (here the cigre MV grid)\n",
    "2. Convert the time series to the dict\n",
    "3. Start the optimization\n",
    "4. plot the results\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Get the grid data\n",
    "We load the cigre medium voltage grid with \"pv\" and \"wind\" generators. Also we set some limits and add a storage with\n",
    "**controllable** == True\n"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import json\n",
    "import os\n",
    "import tempfile\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "import pandapower as pp\n",
    "import pandapower.networks as nw\n",
    "\n",
    "def cigre_grid():\n",
    "    net = nw.create_cigre_network_mv(\"pv_wind\")\n",
    "    # set some limits\n",
    "    min_vm_pu = 0.95\n",
    "    max_vm_pu = 1.05\n",
    "\n",
    "    net[\"bus\"].loc[:, \"min_vm_pu\"] = min_vm_pu\n",
    "    net[\"bus\"].loc[:, \"max_vm_pu\"] = max_vm_pu\n",
    "\n",
    "    net[\"line\"].loc[:, \"max_loading_percent\"] = 100.\n",
    "\n",
    "    # close all switches\n",
    "    net.switch.loc[:, \"closed\"] = True\n",
    "    # add storage to bus 10\n",
    "    pp.create_storage(net, 10, p_mw=0.5, max_e_mwh=.2, soc_percent=0., q_mvar=0., controllable=True)\n",
    "\n",
    "    return net\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Convert the time series to a dict\n",
    "The following functions loads the example time series from the input_file and scales the power accordingly.\n",
    "It then stores the dict to a json file to a temporary folder.\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "def convert_timeseries_to_dict(net, input_file):\n",
    "    # set the load type in the cigre grid, since it is not specified\n",
    "    net[\"load\"].loc[:, \"type\"] = \"residential\"\n",
    "    # change the type of the last sgen to wind\n",
    "    net.sgen.loc[:, \"type\"] = \"pv\"\n",
    "    net.sgen.loc[8, \"type\"] = \"wind\"\n",
    "\n",
    "    # read the example time series\n",
    "    time_series = pd.read_json(input_file)\n",
    "    time_series.sort_index(inplace=True)\n",
    "    # this example time series has a 15min resolution with 96 time steps for one day\n",
    "    n_timesteps = time_series.shape[0]\n",
    "\n",
    "    n_load = len(net.load)\n",
    "    n_sgen = len(net.sgen)\n",
    "    p_timeseries = np.zeros((n_timesteps, n_load + n_sgen), dtype=float)\n",
    "    # p\n",
    "    load_p = net[\"load\"].loc[:, \"p_mw\"].values\n",
    "    sgen_p = net[\"sgen\"].loc[:7, \"p_mw\"].values\n",
    "    wind_p = net[\"sgen\"].loc[8, \"p_mw\"]\n",
    "\n",
    "    p_timeseries_dict = dict()\n",
    "    for t in range(n_timesteps):\n",
    "        # print(time_series.at[t, \"residential\"])\n",
    "        p_timeseries[t, :n_load] = load_p * time_series.at[t, \"residential\"]\n",
    "        p_timeseries[t, n_load:-1] = - sgen_p * time_series.at[t, \"pv\"]\n",
    "        p_timeseries[t, -1] = - wind_p * time_series.at[t, \"wind\"]\n",
    "        p_timeseries_dict[t] = p_timeseries[t, :].tolist()\n",
    "\n",
    "    time_series_file = os.path.join(tempfile.gettempdir(), \"timeseries.json\")\n",
    "    with open(time_series_file, 'w') as fp:\n",
    "        json.dump(p_timeseries_dict, fp)\n",
    "\n",
    "    return net, p_timeseries_dict\n"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Start the optimization \n",
    "Here we start the optimization for the 15min resolution time series. Since we have 96 time steps and 15 min resolution\n",
    "we set n_timesteps=96 and time_elapsed=.25 as a quarter of an hour.\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "# open the cigre mv grid\n",
    "net = cigre_grid()\n",
    "# convert the time series to a dict and save it to disk\n",
    "input_file = \"cigre_timeseries_15min.json\"\n",
    "net, p_timeseries = convert_timeseries_to_dict(net, input_file)\n",
    "# run the PowerModels.jl optimization\n",
    "# n_time steps = 96 and time_elapsed is a quarter of an hour (since the time series are in 15min resolution)\n",
    "storage_results = pp.runpm_storage_opf(net, n_timesteps=96, time_elapsed=0.25)\n"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Store the results (optionally) \n",
    "Store the results to a json file\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "def store_results(storage_results, grid_name):\n",
    "    for key, val in storage_results.items():\n",
    "        file = grid_name + \"_strg_res\" + str(key) + \".json\"\n",
    "        print(\"Storing results to file {}\".format(file))\n",
    "        print(val)\n",
    "        val.to_json(file)\n",
    "# store the results to disk optionally\n",
    "store_results(storage_results, \"cigre_ts\")\n"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Plot the results \n",
    "Plot the optimization results for the storage.\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "def plot_storage_results(storage_results):\n",
    "    n_res = len(storage_results.keys())\n",
    "    fig, axes = plt.subplots(n_res, 2)\n",
    "    if n_res == 1:\n",
    "        axes = [axes]\n",
    "    for i, (key, val) in enumerate(storage_results.items()):\n",
    "        res = val\n",
    "        axes[i][0].set_title(\"Storage {}\".format(key))\n",
    "        el = res.loc[:, [\"p_mw\", \"q_mvar\", \"soc_mwh\"]]\n",
    "        el.plot(ax=axes[i][0])\n",
    "        axes[i][0].set_xlabel(\"time step\")\n",
    "        axes[i][0].legend(loc=4)\n",
    "        axes[i][0].grid()\n",
    "        ax2 = axes[i][1]\n",
    "        patch = plt.plot([], [], ms=8, ls=\"--\", mec=None, color=\"grey\", label=\"{:s}\".format(\"soc_percent\"))\n",
    "        ax2.legend(handles=patch)\n",
    "        ax2.set_label(\"SOC percent\")\n",
    "        res.loc[:, \"soc_percent\"].plot(ax=ax2, linestyle=\"--\", color=\"grey\")\n",
    "        ax2.grid()\n",
    "\n",
    "    plt.show()\n",
    "# plot the result\n",
    "plot_storage_results(storage_results)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  }
 ],
 "metadata": {
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  },
  "kernelspec": {
   "name": "python3",
   "language": "python",
   "display_name": "Python 3"
  },
  "pycharm": {
   "stem_cell": {
    "cell_type": "raw",
    "source": [],
    "metadata": {
     "collapsed": false
    }
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}